'''
Created on 10/09/2009

@author: Edward
'''
from xml.etree import ElementTree
from datastore.resource import Resource, ResourceDict
import os

ATTRIBUTE_TARGET_NAMESPACE = 'targetNamespace'
ATTRIBUTE_NAME = 'name'
ATTRIBUTE_TYPE = 'type'
ATTRIBUTE_BASE = 'base'

class Element(object):
    '''
    Element in a xml document.
    '''
    
    def __init__(self, name=None, type=None, ns=None, array=False):
        # name of the element, e.g the tag name excluding any namespace information
        self.name = name
        # the fully qualified type of the element
        self.type = type
        # the namespace which the element belongs to. E.g http://application.response.webservice.spear.lx.dse.vic.gov.au
        self.ns = ns
        # if the element is a array
        self.array = array

    def get_fully_qualified_name(self):
        return '{'+self.ns+'}' + self.name
    
    def to_resource(self):
        rs = ResourceDict()
        rs.name = self.name
        rs.type = self.type
        rs.ns = self.ns
        rs.array = self.array
        rs.fully_qualified_name = self.get_fully_qualified_name()
        return rs
        
    def __cmp__(self, other):
        '''
        Should return a negative integer if self < other, zero if self == other,
        a positive integer if self > other.
        '''
        if other is None:
            # if other is None, less just say that self is greater than other
            return 1
        elif str(other) == str(self):
            return 0
        else:
            return -1
    
    def __str__(self):
        return "{0} {1} {2}".format(self.get_fully_qualified_name(), self.type, str(self.array))
    
class ComplexType(object):
    '''
    Complex Type in a xml document.
    '''
    
    def __init__(self):
        self.__name = None
        # list of elements as sequence that elements appear in the soap message are important
        self.elements = []
        # namespace that the complex type belongs to
        self.namespace = None
        # if the complex type extends another type, then the fully qualified name of that complex type is stored here
        self.extension_type = None
        
    def get_fully_qualified_name(self):
        return "{" + self.namespace + "}" + self.name

class WSDL(object):
    """
    WSDL Class to parse WSDL.
    
    Extracts information from a WSDL required to generate input and parse output messages from a SPEAR A2A webservice.
    
    """
    
    def __init__(self, wsdl_string):
        self.wsdl_string = wsdl_string
        # dictionary containing Element data of each element in the input and output of a webservice operation.
        # The key is similar to the xpath to a element in a soap message, excluding only the namespace information
        # in the xpath. e.g. key = getApplicationResponse.getApplicationReturn.application.spearReference
        # and the value is a instance of the spear.wsdl.Element type
        self.__element_dictionary = {}
        # dictionary containing the complex types defined for this webservice. The key is the fully
        # qualified name of the complex type and the value is the complex type itself
        self.__types_dictionary = {}
        
        # parse the wsdl file
        self.parse_wsdl()
    
    def parse_wsdl(self):
        """
        Parses WSDL File.
        
        Parses the WSDL File to extract the complex types and namespace information. This works by extracting all
        complex types into a 'types dictionary'. Then starting from the input and output types of the webservice operations,
        traverse through the elements and add the associated namespace of the element and the path to the element into a
        'namespace dictionary'
        
        Return: None
        """
        
        wsdl_xml = self.pre_parse_xml_to_add_namespace_data_to_elements()
        self.parse_all_complex_types(wsdl_xml)
        self.populate_element_dictionary(wsdl_xml)
 
    def pre_parse_xml_to_add_namespace_data_to_elements(self):
        """
        Return soap response as resource.
        
        The resource returned is the 'body' section of the soap response.
        The tag names (excluding namespace) are stored as attributes of
        the resource and the text of a tag is stored as its value. This
        makes navigating the response easier through the Resource
        API
        
        Return: xml.etree.elementtree.Element
        """
        
        # code below requires to read xml from file, so creating a temporary file here
        wsdl_path_name = 'temp'
        temp = open(wsdl_path_name, 'wb')
        temp.write(self.wsdl_string)
        temp.close()
        temp = None

####################################################################################################################
# NOTES
####################################################################################################################
#         The 'start' and 'end' events represent opening and closing element. They are accompanied by the respective 
#         Element instance.  By default, only 'end' events are generated
#         The 'start-ns' and 'end-ns' events notify about namespace declarations. They do not come with Elements.
#         Instead, the value of the 'start-ns' event is a tuple (prefix, namespaceURI) that designates the 
#         beginning of a prefix-namespace mapping. The corresponding 'end-ns' event does not have a value (None).
#         It is common practice to use a list as namespace stack and pop the last entry on the 'end-ns' event
#        
#        Example of the events
#          <root>
#            <element key='value'>text</element>
#            <element>text</element>tail
#            <empty-element xmlns="http://testns/" />
#          </root>
#        
#          >>> events = ("start", "end", "start-ns", "end-ns")
#          >>> context = etree.iterparse(StringIO(xml), events=events)
#          >>> for action, elem in context:
#          ...     if action in ('start', 'end'):
#          ...         print("%s: %s" % (action, elem.tag))
#          ...     elif action == 'start-ns':
#          ...         print("%s: %s" % (action, elem))
#          ...     else:
#          ...         print(action)
#          start: root
#          start: element
#          end: element
#          start: element
#          end: element
#          start-ns: ('', 'http://testns/')
#          start: {http://testns/}empty-element
#          end: {http://testns/}empty-element
#          end-ns
#          end: root
 
        # pre-parse wsdl and attach namespace information to each element
        root_element_tree = None
        ns_map = []       
        events = "start", "end", "start-ns", "end-ns"        
        for event, elem in ElementTree.iterparse(wsdl_path_name, events):
            if event == "start-ns":
                # add the namespace mapping
                ns_map.append(elem)
            elif event == "end-ns":
                # pop up the last added namespace mapping
                ns_map.pop()
            elif event == "start":
                if root_element_tree is None:
                    root_element_tree = elem
                # add the list of namespace mappings to the element so it has access to it through ns_map variable
                elem.ns_map = dict(ns_map)
            elif event == "end":
                # do nothing
                pass
        # remove temp file    
        os.remove(wsdl_path_name)
        return root_element_tree
    
    def populate_element_dictionary(self, wsdl_xml):
        """
        Populates the element dictionary
        
        Populates the element dictionary with selected data for each element in the types used in the 
        input and output of a webservice operation. 
        
        Return: None
        
        Parameters:
        wsdl_xml -- xml.etree.elementtree.Element: root element of the wsdl file.
        """
        
        wsdl_types = wsdl_xml.find('./{http://schemas.xmlsoap.org/wsdl/}types')
        schemas = wsdl_types.findall('./{http://www.w3.org/2001/XMLSchema}schema')
        for schema in schemas:
            namespace = schema.get(ATTRIBUTE_TARGET_NAMESPACE)
            # ASSUMPTION FOR SPEAR WSDLS:
            # WEBSPHERE generates a complex type (i.e. complex type with no name) for the
            # webservice operation parameters. These types don't have names, but are defined dynamically
            # where the element is defined. e.g.
            # <element name="arg">
            #    <complexType>
            #        <element name="element_of_arg" type="...." />
            #    </complexType>
            # </element>
            # Hence to populate the element_dictionary we start from the webservice parameter as the root
            # complex type and work our way through its complex_types_as_elements. In order to do for 'element'
            # that are direct children of the schema node
            complex_types_as_elements = schema.findall('./{http://www.w3.org/2001/XMLSchema}element')
            for complex_type_as_element in complex_types_as_elements:
                element_name = complex_type_as_element.get(ATTRIBUTE_NAME)
                element_def = Element(element_name, None, namespace, False)   
                self.element_dictionary[element_name] = element_def.to_resource()
                elements = complex_type_as_element.findall('.//{http://www.w3.org/2001/XMLSchema}element')
                for element in elements:
                    self.populate_element_dictionary_for_element(element, element_name, namespace)        
                
    def populate_element_dictionary_for_extension(self, element, path, namespace):
        """
        Populates the element dictionary with extension element information
        
        Populates the element dictionary with data of the current extension element.
        
        Return: None
        
        Parameters:
        element -- xml.etree.elementtree.Element: extension element to populate the element dictionary.
        path -- String: xpath to a element, excluding only the namespace information
        namespace -- String: Namespace of the current element
        """            
        complex_type_name = element.get(ATTRIBUTE_BASE)
        fully_qualified_name_of_type = self.get_fully_qualified_name(element, complex_type_name)
        if fully_qualified_name_of_type in self.types_dictionary:
            # get complex type and continue to populate the namespace dictionary down this path
            ct = self.types_dictionary[fully_qualified_name_of_type]
            self.populate_element_dictionary_for_complex_type(ct, path)
                    
    def populate_element_dictionary_for_element(self, element, path, namespace):
        """
        Populates the element dictionary with element information
        
        Populates the element dictionary with data of the current element.
        
        Return: None
        
        Parameters:
        element -- xml.etree.elementtree.Element: element to populate the element dictionary.
        path -- String: xpath to a element, excluding only the namespace information
        namespace -- String: Namespace of the current element
        """        
        element_name = element.get(ATTRIBUTE_NAME)
        
        # path of the element is the path to the current element plus its name
        new_path = ".".join([path,element_name])
        element_def = Element(element_name, '{'+namespace+'}'+element_name, namespace, False)        
        self.element_dictionary[new_path] = element_def.to_resource()
        
        # get the type of the element. This might be prefixed by a namespace prefix e.g tns:type_name
        type = element.get(ATTRIBUTE_TYPE)
        # get the fully qualified name of the type
        fully_qualified_name_of_type = self.get_fully_qualified_name(element, type)
        # if the type of the element is a a2a complex type
        if fully_qualified_name_of_type in self.types_dictionary:
            # get complex type and continue to populate the namespace dictionary down this path
            ct = self.types_dictionary[fully_qualified_name_of_type]
            self.populate_element_dictionary_for_complex_type(ct, new_path)
            
    def populate_element_dictionary_for_complex_type(self, ct, path):
        """
        Populates the element dictionary with complex type information
        
        Populates the element dictionary with the elements and extensions of the current complex type.
        
        Return: None
        
        Parameters:
        ct -- spear.wsdl.ComplexType: Complex type to add information from to element dictionary.
        path -- String: xpath to a element, excluding only the namespace information
        """
        # add all the elements of the complex type to the element dictionary
        for ct_element in ct.elements:
            element_name = ct_element.name
            # path of the element is the path to the current element plus its element name
            new_path = ".".join([path[:], element_name])
            # save namespace against path to element
            self.element_dictionary[new_path] = ct_element.to_resource()
            type = ct_element.type
            # if the type of the element is a a2a complex type
            if type in self.types_dictionary:
                # get complex type and continue to populate the namespace dictionary down this path
                new_ct = self.types_dictionary[type]
                self.populate_element_dictionary_for_complex_type(new_ct, new_path)
        # if this complex type extends another add that information to the element dictionary too
        if ct.extension_type:
            # get complex type and continue to populate the namespace dictionary down this path
            ct = self.types_dictionary[ct.extension_type]
            self.populate_element_dictionary_for_complex_type(ct, path)
                       
    def parse_all_complex_types(self, wsdl_xml):
        """
        Populates the types dictionary with all the statically defined complex types
        
        Populates the types dictionary with all the statically defined complex types. These complexs reflect the 
        SPEAR A2A Types
        
        Return: None
        """         
        wsdl_types = wsdl_xml.find('./{http://schemas.xmlsoap.org/wsdl/}types')
        schemas = wsdl_types.findall('./{http://www.w3.org/2001/XMLSchema}schema')
        for schema in schemas:
            namespace = schema.get(ATTRIBUTE_TARGET_NAMESPACE)           
            complex_types = schema.findall('./{http://www.w3.org/2001/XMLSchema}complexType')
            for complex_type in complex_types:
                ct = self.create_complex_type(complex_type, namespace)
                # add complex type to dictionary
                self.types_dictionary[ct.get_fully_qualified_name()] = ct
    
    def create_complex_type(self, complex_type, namespace):
        # create complex type
        ct = ComplexType()
        ct.name = complex_type.get(ATTRIBUTE_NAME)
        ct.namespace = namespace
        extension_element = complex_type.find('.//{http://www.w3.org/2001/XMLSchema}extension')           
        if extension_element:
            ct.extension_type = self.get_fully_qualified_name(extension_element, extension_element.get(ATTRIBUTE_BASE))
        elements = complex_type.findall('.//{http://www.w3.org/2001/XMLSchema}element')
        for element in elements:
            e = Element()
            e.name = element.get(ATTRIBUTE_NAME)
            type = element.get(ATTRIBUTE_TYPE)
            e.ns = ct.namespace
            e.type = self.get_fully_qualified_name(element, type)
            if element.get('maxOccurs') == 'unbounded':
                e.array = True
            # add element to complex type
            ct.elements.append(e)
        return ct   
    
    def get_element_dictionary(self):
        return self.__element_dictionary
    
    def get_types_dictionary(self):
        return self.__types_dictionary
    
    def get_fully_qualified_name(self, element, name):
        """
        Return fully qualified name 
        
        The name could have a namespace prefix. This method strips the prefix, looks up
        the namespace for that prefix and prefixes the namespace to the name. e.g
        tns:name returns {http://namespace.com}name
        
        Return: String
        
        Keyword arguments:
        element -- : xml.etree.elementtree.Element:
        name -- String: the name which could also have a namespace prefix
        """         
        if name.partition(':')[0] == name:
            return name
        else:
            return "{" + element.ns_map[name.partition(':')[0]] + "}" + name.partition(':')[2]

    element_dictionary = property(get_element_dictionary, None, None, None)
    types_dictionary = property(get_types_dictionary, None, None, None)